//
//  AppController.m
//  HypERLtrace
//
//  Created by Vladimir Sarpe on 29/07/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "AppController.h"

@implementation AppController

- (NSMutableDictionary*)searchTree:(NSMutableArray*)tree forProc:(NSString*)proc {
	if ([tree count] < 1)
		return nil;
	else {
		NSMutableDictionary* found = nil;
		for (id node in tree) {
			if ([[node objectForKey:@"Process"] isEqualToString:proc]) {
				found = node;
				break;
			}
			else {
				found = [self searchTree:[node objectForKey:@"child"] forProc:proc];
				if (found != nil)
					break;
			}
		}
		return found;
	}
}

- (void)breakEvent:(NSString*) event {
	//NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	NSScanner* scanner;
	NSString* token;
	NSDictionary* dictionary;
	NSArray* keys;
	NSArray* objects;
	NSMutableArray* brokenEvent = [[NSMutableArray alloc] init];
	scanner = [NSScanner scannerWithString:event];
	while (![scanner isAtEnd]) {
		[scanner setScanLocation: [scanner scanLocation]+2];
		if ([scanner scanUpToString:@"||" intoString:&token])
			[brokenEvent addObject:token];
	}
	NSString* type = [brokenEvent objectAtIndex:0];
	NSString* timestamp = [brokenEvent objectAtIndex:1];
	NSString* process = [brokenEvent objectAtIndex:2];
	NSCharacterSet* bad_chars = [NSCharacterSet characterSetWithCharactersInString:@"{}<>\""];
	process = [process stringByTrimmingCharactersInSet:bad_chars];
	NSString* breaker = @"™";  //@"\xAA";
	NSString* receiver = @"";
	NSString* details = @"";
	NSMutableDictionary* node = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:process, @"-", @"-", @"-", @" ", @" ", @" ", [[NSMutableArray alloc] init], @"", nil] forKeys:[NSArray arrayWithObjects:@"Process", @"Sends", @"Receives", @"Spawns", @"Module", @"Function", @"Argumens", @"child", @"vals", nil]];
	if ([actorHash objectForKey:process] == nil) {
		NSMutableDictionary* vals = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"0", [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], nil] forKeys:[NSArray arrayWithObjects:@"lifetime", @"send", @"receive", @"spawn", nil]];
		[actorHash setObject:vals forKey:process];
		if (![procTree containsObject:node]) {
			[node setObject:[actorHash objectForKey:process] forKey:@"vals"];
			[procTree addObject:node];
		}
	}
	if ([type isEqualToString:@"send"]){
		int sends = [[[actorHash objectForKey:process] objectForKey:@"send"] intValue] + 1;
		[[actorHash objectForKey:process] setObject:[NSNumber numberWithInt:sends] forKey:@"send"];
		details = [brokenEvent objectAtIndex:4];
		receiver = [brokenEvent objectAtIndex:3];
		receiver = [receiver stringByTrimmingCharactersInSet:bad_chars];
		if ([actorHash objectForKey:receiver] == nil) {
			NSMutableDictionary* vals = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"0", [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], nil] forKeys:[NSArray arrayWithObjects:@"lifetime", @"send", @"receive", @"spawn", nil]];
			[actorHash setObject:vals forKey:receiver];
		}
		NSMutableDictionary* recnode = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:receiver, @"-", @"-", @"-", @" ", @" ", @" ", [[NSMutableArray alloc] init], nil] forKeys:[NSArray arrayWithObjects:@"Process", @"Sends", @"Receives", @"Spawns", @"Module", @"Function", @"Argumens", @"child", nil]];
		if (![self searchTree:procTree forProc:receiver]) {
			[procTree addObject:recnode];
		}
	}
	else if ([type isEqualToString:@"spawn"]) {
		receiver = [brokenEvent objectAtIndex:3];
		receiver = [receiver stringByTrimmingCharactersInSet:bad_chars];
		
		//update actor hash with the new spawned process
		NSMutableDictionary* vals = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:timestamp, [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], [NSNumber numberWithInt:0], nil] forKeys:[NSArray arrayWithObjects:@"lifetime", @"send", @"receive", @"spawn", nil]];
		[actorHash setObject:vals forKey:receiver];
		[[actorHash objectForKey:process] setObject:[NSNumber numberWithInt:[[[actorHash objectForKey:process] objectForKey:@"spawn"] intValue] + 1] forKey:@"spawn"];
		details = [brokenEvent objectAtIndex:4];
		
		//remove unwanted characters
		details = [details stringByReplacingOccurrencesOfString:breaker withString:@""]; 
		details = [details stringByReplacingOccurrencesOfString:@" " withString:@""];
		
		//set up information for child node
		NSString* sends = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:receiver] objectForKey:@"send"] intValue]];
		NSString* spawns = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:receiver] objectForKey:@"spawn"] intValue]];
		NSString* receives = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:receiver] objectForKey:@"receives"] intValue]];
		NSString* arguments;
		NSString* mod_func;
		NSScanner* det_scanner = [NSScanner scannerWithString:details];
		[det_scanner scanUpToString:@"[" intoString:&mod_func];
		[det_scanner setScanLocation:[det_scanner scanLocation]+1];
		[det_scanner scanUpToString:@"\n" intoString:&arguments];
		NSArray* mod_func_array = [mod_func componentsSeparatedByString:@","];
		NSString* module = [mod_func_array objectAtIndex:0];
		module = [module stringByTrimmingCharactersInSet:bad_chars];
		NSString* function = [mod_func_array objectAtIndex:1];
		
		//load node into search tree
		NSMutableDictionary* newNode = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:receiver, sends, receives, spawns, module, function, arguments, [[NSMutableArray alloc] init], nil] forKeys:[NSArray arrayWithObjects:@"Process", @"Sends", @"Receives", @"Spawns", @"Module", @"Function", @"Arguments", @"child", nil]];
		[[[self searchTree:procTree forProc:process] objectForKey:@"child"] addObject:newNode];
	}
	else if ([type isEqualToString:@"receive"]) {
		[[actorHash objectForKey:process] setObject:[NSNumber numberWithInt:[[[actorHash objectForKey:process] objectForKey:@"receive"] intValue] + 1] forKey:@"receive"];
		details = [brokenEvent objectAtIndex:3];
	}
	else if ([type isEqualToString:@"die"]) {
		NSString* death = [NSString stringWithFormat:@"-%@", timestamp];
		NSMutableDictionary* life = [actorHash objectForKey:process];
		[life setObject:[[life objectForKey:@"lifetime"] stringByAppendingString:death] forKey:@"lifetime"];
		//[[actorHash objectForKey:process] setObject:[[[actorHash objectForKey:process] objectForKey:@"lifetime"] stringByAppendingString:death] forKey:@"lifetime"];
	}
	details = [details stringByReplacingOccurrencesOfString:breaker withString:@""]; 
	details = [details stringByReplacingOccurrencesOfString:@" " withString:@""];
	/*NSMutableAttributedString* typel = [[NSMutableAttributedString alloc] initWithString:type];
	[typel addAttribute:NSLinkAttributeName value:type range:NSMakeRange(0, [typel length])];
	[typel addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:NSMakeRange(0, [typel length])];
	[typel addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:NSMakeRange(0, [typel length])];*/
	keys = [NSArray arrayWithObjects:@"type", @"process", @"receiver", @"timestamp", @"details", nil];
	objects = [NSArray arrayWithObjects:type, process, receiver, timestamp, details,  nil];
	dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
	//[pool drain];
	if ([timestamp intValue] > [lastevent intValue])
		lastevent = timestamp;
	[graphEvents addObject:timestamp];
	[events addObject:dictionary];
}

- (IBAction)open:(id)sender {
	globalSender = sender;
	//NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	NSArray* fileTypes = [NSArray arrayWithObjects:@"ev", @"txt", @"tr", nil];
	NSOpenPanel* openPanel = [NSOpenPanel openPanel];
	[openPanel setAlphaValue:0.95];
	[openPanel setTitle:@"Select a trace file to open"];
	[openPanel setCanChooseFiles:YES];
	[openPanel setCanChooseDirectories:NO];
	[openPanel setCanCreateDirectories:YES];
	[openPanel setAllowsMultipleSelection:NO];
	int i;
	if([openPanel runModalForDirectory:nil file:nil types:fileTypes] == NSOKButton) {
		NSArray* files = [openPanel filenames];
		[progress startAnimation:sender];
		[searchField setStringValue:@""];
		for (i = 0; i < [files count]; i++) {
			NSString* fileName = [files objectAtIndex:i];
			[window setTitle:[NSString stringWithFormat:@"HypERLtrace - %@", [fileName lastPathComponent]]];
			NSString* event;
			NSString* newFile;
			NSFileManager* fileManager = [NSFileManager defaultManager];
			if ([[fileName substringFromIndex:[fileName length]-2] isEqualToString:@"ev"])
				newFile = fileName;
			else if ([fileManager fileExistsAtPath:[[fileName substringToIndex:[fileName length]-3] stringByAppendingString:@"ev"]])
				newFile = [[fileName substringToIndex:[fileName length]-3] stringByAppendingString:@"ev"];
			else if ([fileManager fileExistsAtPath:[[fileName substringFromIndex:[fileName length]-2] stringByAppendingString:@"ev"]])
				newFile = [[fileName substringToIndex:[fileName length]-2] stringByAppendingString:@"ev"];
			else if ([fileManager fileExistsAtPath:[fileName stringByAppendingString:@".ev"]])
				newFile = [fileName stringByAppendingString:@".ev"];
			else {
				NSTask* process = [[NSTask alloc] init];
				NSString* directory = [fileManager currentDirectoryPath];
				NSString* scriptPath = [directory stringByAppendingString:@"/script.pl"];
				if (![fileManager fileExistsAtPath:scriptPath]) {
					directory = [directory stringByDeletingLastPathComponent];
					directory = [directory stringByDeletingLastPathComponent];
				}
				[fileManager changeCurrentDirectoryPath:directory];
				[process setLaunchPath:@"/usr/bin/perl"];
				NSArray* args = [NSArray arrayWithObjects:@"script.pl", fileName, @"events", nil];
				[process setArguments:args];
				[process launch];
				[process waitUntilExit];
				newFile = [fileName stringByAppendingString:@".ev"];
			}			
			lastevent = @"0";
			actorHash = [[NSMutableDictionary alloc] init];
			procTree = [NSMutableArray arrayWithCapacity:[events count]];
			NSString* fileContent = [[NSString alloc] initWithContentsOfFile:newFile];
			NSCharacterSet* newLine = [NSCharacterSet newlineCharacterSet];
			NSScanner* scanner = [NSScanner scannerWithString:fileContent];
			NSString* count_str;
			[scanner scanUpToCharactersFromSet:newLine intoString:&count_str];
			events = [NSMutableArray arrayWithCapacity:[count_str intValue]];
			graphEvents = [NSMutableArray arrayWithCapacity:[count_str intValue]];
			while (![scanner isAtEnd]) {
				if ([scanner scanUpToCharactersFromSet:newLine intoString:&event])
					[self breakEvent:event];
			}
			for (id process in actorHash) {
				NSString* sends = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:process] objectForKey:@"send"] intValue]];
				NSString* spawns = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:process] objectForKey:@"spawn"] intValue]];
				NSString* receives = [NSString stringWithFormat:@"%d", [[[actorHash objectForKey:process] objectForKey:@"receive"] intValue]];
				[[self searchTree:procTree forProc:process] setObject:sends forKey:@"Sends"];
				[[self searchTree:procTree forProc:process] setObject:receives forKey:@"Receives"];
				[[self searchTree:procTree forProc:process] setObject:spawns forKey:@"Spawns"];

			}
			[[tableView window] makeFirstResponder:tableView];
			[[tableView window] setAcceptsMouseMovedEvents:YES]; 
			[tableView setDataSource:dataSource];
			[dataSource updateData:events];
			[tableView updateLifeTimes:actorHash];
			[tableView reloadData];
			[graphView loadData:graphEvents andLast:[lastevent intValue] andColor:[NSColor blackColor] andBackground:[NSColor colorWithCalibratedWhite:0.8 alpha:1.0]];
			[arcGraphView loadData:graphEvents andLast:[lastevent intValue] andColor:[NSColor whiteColor] andBackground:[NSColor blackColor]];
			//NSLog(@"res %f", [[[arcView window] screen] frame].size.width);
			
			[arcView setActors:actorHash andEvents:events andTree:procTree andResWidth:[[[arcView window] screen] frame].size.width andResHeight:[[[arcView window] screen] frame].size.height andLast:[lastevent intValue] andArgs:@"fam"];
			[[arcView window] setAcceptsMouseMovedEvents:YES];
			[seqB setState:NSOnState];
			[hierB setState:NSOffState];
			
			[outlineView setDataSource:procDataSource];
			[procDataSource updateData:procTree];
			[outlineView reloadData];
		}
	}
	//[pool drain];
	[progress stopAnimation:sender];
}

- (NSMutableArray*)filterContent:(NSDictionary*)searchDict {
	NSMutableArray* matched = [NSMutableArray arrayWithCapacity:[events count]];
	NSEnumerator* eventEnum = [events objectEnumerator];
	NSString* searchIn;
	id event;
	while (event = [eventEnum nextObject]) {
		NSMutableDictionary* searchDictCopy = [NSMutableDictionary dictionaryWithDictionary:searchDict];
		for (id key in searchDict) {
			BOOL isMatched = NO;
			NSArray* mult = [[searchDict valueForKey:key] componentsSeparatedByString:@","];
			for (NSString* searchFor in mult) {
				if ([key isEqualToString:@"all"]) {
					for (id column in event) {
						NSString* searchIn = [event valueForKey:column];
						if ([searchIn rangeOfString:searchFor].location != NSNotFound) {
							isMatched = YES;
							break;
						}
					}
				}
				else if ([key isEqualToString:@"timestamp"]) {
					searchFor = [searchFor stringByReplacingOccurrencesOfString:@"ms" withString:@"000"];
					searchFor = [searchFor stringByReplacingOccurrencesOfString:@"s" withString:@"000000"];
					NSArray* range = [searchFor componentsSeparatedByString:@"-"];
					if ([range count] < 2) {
						if ([[event valueForKey:key] isEqualToString:searchFor]) {
							isMatched = YES;
							break;
						}
					}
					else {
						NSString* searchIn = [event valueForKey:key];
						if ([searchIn intValue] > [[range objectAtIndex:1] intValue])
							break;
						if ([searchIn intValue] >= [[range objectAtIndex:0] intValue] && [searchIn intValue] <= [[range objectAtIndex:1] intValue]) {
							isMatched = YES;
							break;
						}
					}
				}
				else {
					searchIn = [event valueForKey:key];
					if ([searchIn rangeOfString:searchFor].location != NSNotFound) {
						isMatched = YES;
						break;
					}
					if ([key isEqualToString:@"process"]) {
						searchIn = [event valueForKey:@"receiver"];
						if ([searchIn rangeOfString:searchFor].location != NSNotFound) {
							isMatched = YES;
							break;
						}
					}
				}
				if (isMatched) {
					break;
				}
			}
			if (isMatched) 
				[searchDictCopy setObject:@"Y" forKey:key];
			else
				[searchDictCopy setObject:@"N" forKey:key];
		}
		BOOL isAllMatched = YES;
		for (id key in searchDictCopy) {
			if (![[searchDictCopy valueForKey:key] isEqualToString:@"Y"])
				isAllMatched = NO;
		}
		if (isAllMatched) 
			[matched addObject:event];
	}
	return matched;
}

- (NSMutableDictionary*)tokenize:(NSString*)searchString {
	NSMutableDictionary* tokens = [[NSMutableDictionary alloc] init];
	NSScanner* outScanner = [NSScanner scannerWithString:searchString];
	while (![outScanner isAtEnd]) {
		NSString* element = [[NSString alloc] init];
		NSString* type = @"";
		NSString* query = @"none";
		[outScanner scanUpToString:@" " intoString:&element];
		NSScanner* inScanner = [NSScanner scannerWithString:element];
		while (![inScanner isAtEnd]) {
			[inScanner scanUpToString:@":" intoString:&type];
			if (![inScanner isAtEnd]) {
				[inScanner setScanLocation:[inScanner scanLocation]+1];
				[inScanner scanUpToString:@" " intoString:&query];
			}
			else {
				query = type;
				type = @"all";
			}
		}
		[tokens setValue:query forKey:type];
	}
	return tokens;
}

- (void)performSearch:(NSString*) searchString {
	[searchField setStringValue:searchString];
	[progress startAnimation:globalSender];
	BOOL query = NO;
	NSMutableArray* matchedObjects;
	if ((searchString == nil) || ([searchString isEqualToString:@""])) {
		matchedObjects = events;
		query = NO;
	}
	else {
		query = YES;
		matchedObjects = [self filterContent:[self tokenize:searchString]];
	}
	[graphView notifyMatched:matchedObjects andQuery:query];
	[graphView setNeedsDisplay:YES];
	[dataSource updateData:matchedObjects];
	[tableView reloadData];
	[progress stopAnimation:globalSender];
}

- (IBAction)search:sender {
	[self performSearch:[searchField stringValue]];
}

//Show Advanced Search Window
- (IBAction)showAdvSearch:sender {
	if (![advSearchPanel isVisible]) 
		[advSearchPanel makeKeyAndOrderFront:self];
	else 
		[advSearchPanel orderOut:self];
}

//Builds a search string from the advanced search form and runs a search
- (IBAction)advSearch:sender {
	// build search string
	NSString* searchString = [[advSearchForm cellAtIndex:0] stringValue];
	if (!([[[advSearchForm cellAtIndex:1] stringValue] isEqualToString:@""]) ) {
		searchString = [searchString stringByAppendingString:@" type:"];
		searchString = [searchString stringByAppendingString:[[advSearchForm cellAtIndex:1] stringValue]];
	}
	if (!([[[advSearchForm cellAtIndex:2] stringValue] isEqualToString:@""]) ) {
		searchString = [searchString stringByAppendingString:@" process:"];
		searchString = [searchString stringByAppendingString:[[advSearchForm cellAtIndex:2] stringValue]];
	}
	if (!([[[advSearchForm cellAtIndex:3] stringValue] isEqualToString:@""]) ) {
		searchString = [searchString stringByAppendingString:@" timestamp:"];
		searchString = [searchString stringByAppendingString:[[advSearchForm cellAtIndex:3] stringValue]];
	}
	if (!([[[advSearchForm cellAtIndex:4] stringValue] isEqualToString:@""]) ) {
		searchString = [searchString stringByAppendingString:@" details:"];
		searchString = [searchString stringByAppendingString:[[advSearchForm cellAtIndex:4] stringValue]];
	}
	//Run Search
	[self performSearch:searchString];
}

//Clears advanced search form
- (IBAction)clearSearchForm:sender {
	[[advSearchForm cellAtIndex:0] setStringValue:@""];
	[[advSearchForm cellAtIndex:1] setStringValue:@""];
	[[advSearchForm cellAtIndex:2] setStringValue:@""];
	[[advSearchForm cellAtIndex:3] setStringValue:@""];
	[[advSearchForm cellAtIndex:4] setStringValue:@""];
}	

- (IBAction)tab:(id)sender {
	if (selected == 0) {
		[tabView selectFirstTabViewItem:globalSender];
	}
	else if (selected == 1) {
		[tabView selectLastTabViewItem:globalSender];
	}
}

- (IBAction)eventMenu:(id)sender {
	NSString* ident;
	if ([[sender title] isEqualToString:@"Type"])
		ident = @"type";
	else if ([[sender title] isEqualToString:@"Process"])
		ident = @"process";
	else if ([[sender title] isEqualToString:@"Receiver"])
		ident = @"receiver";
	else if ([[sender title] isEqualToString:@"Time"])
		ident = @"timestamp";
	else if ([[sender title] isEqualToString:@"Details"])
		ident = @"details";
	else
		ident = @"";
	if ([sender state] == NSOnState) {
		[sender setState:NSOffState];
		[[tableView tableColumnWithIdentifier:ident] setHidden:YES];
	}
	else {
		[sender setState:NSOnState];
		[[tableView tableColumnWithIdentifier:ident] setHidden:NO];
	}
}

- (IBAction)processMenu:(id)sender{
	if ([sender state] == NSOnState) {
		[sender setState:NSOffState];
		[[outlineView tableColumnWithIdentifier:[sender title]] setHidden:YES];
	}
	else {
		[sender setState:NSOnState];
		[[outlineView tableColumnWithIdentifier:[sender title]] setHidden:NO];
	}
}

- (IBAction)arcView:(id)sender {
	[arcView enterFullScreenMode:[[arcView window] screen] withOptions:nil];
	[arcView notifyScreen:FALSE];
	[arcView setNeedsDisplay:TRUE];

}

- (IBAction)exitFSMode:(id)sender {
	[arcView exitFullScreenModeWithOptions:nil];
	[arcView notifyScreen:FALSE];
}

- (IBAction)windowMode:(id)sender {
	[arcViewWindow makeKeyAndOrderFront:(id)self];
	[arcView notifyScreen:FALSE];

	//[arcViewWindow 
}

- (IBAction)seqView:(id)sender {
	[seqB setState:NSOnState];
	[hierB setState:NSOffState];
	[arcView notifyToggle:@"seq"];
}

- (IBAction)hierView:(id)sender {	
	[hierB setState:NSOnState];
	[seqB setState:NSOffState];
	[arcView notifyToggle:@"fam"];
}
	
@end
